#include "ofApp.h"

namespace embree {
  Device * gEmbreeDevice = 0;
}

static inline embree::Vec3f toEmbreeVec3f(ofVec3f vector) {
  return embree::Vec3f(vector.x, vector.y, vector.z);
}

static inline embree::Col3f toEmbreeCol3f(ofColor color) {
  return embree::Col3f(color.r, color.g, color.b);
}

namespace Embree {

class Renderer {
public:
  Renderer();
  virtual ~Renderer();

  void setup    (ofCamera & cam, int numThreads = 0);
  void exit     ();
  void update   (float focalDistance, float lensRadius);
  void draw     (int x, int y);
  void renderGL ();

  void buildScene ();
  void reset      ();

  void loadScene     (string filepath);
  void loadBackPlate (string filepath);

  void addHDRILight  (string filepath, ofColor luminosity);
  void addQuadLight  (ofPoint position, ofPoint u, ofPoint v, ofColor luminosity);
  // TODO: addAmbientLight
  // TODO: addPointLight
  // TODO: addDirectionalLight
  // TODO: addDistantLight
  // TODO: addTriangleLight

  embree::Handle<embree::Device::RTShape> addSphere (embree::Device::RTMaterial material, ofSpherePrimitive & sphere);
  embree::Handle<embree::Device::RTShape> addSphere (embree::Device::RTMaterial material, ofPoint pos, float radius, int numTheta = 50, int numPhi = 50);
  embree::Handle<embree::Device::RTShape> addMesh   (embree::Device::RTMaterial material, const ofMesh & mesh);
  embree::Handle<embree::Device::RTShape> addMesh   (embree::Device::RTMaterial material, const ofMesh & mesh, const ofMatrix4x4 & transform);
  embree::Handle<embree::Device::RTShape> addShape  (embree::Device::RTMaterial material, embree::Device::RTShape shape);
  embree::Handle<embree::Device::RTShape> addShape  (embree::Device::RTMaterial material, embree::Device::RTShape shape, const ofMatrix4x4 & transform);
  embree::Handle<embree::Device::RTShape> addShape  (embree::Device::RTMaterial material, embree::Device::RTShape shape, const embree::AffineSpace3f & transform);

  void removeShape  (embree::Device::RTShape shape);
  void removeShapes (vector<embree::Handle<embree::Device::RTShape>> & shapes);
  void clearScene   (bool bRebuild = true);

  void setSize           (int width, int height);
  void setClampRadiance  (float clampRadianceIfMoreThan, float clampRadianceTo);
  void setSamplePerPixel (int spp);
  void setRecursionDepth (int depth);
  void setGamma          (float gamma);
  void setVignetting     (bool bVigneting);

  void setMaxPasses (int numPassMax);

  int   getAccumulation    ();
  float getLastRenderTime  ();
  float getAccumulatedTime ();

  ofFbo & getFbo() { return fbo; }
  vector<embree::Handle<embree::Device::RTPrimitive>> & getPrimitives() { return prims; }

protected:
  embree::Handle<embree::Device::RTScene> createScene  ();
  void                                    updateCamera (float focalDistance, float lensRadius);

  ofCamera * cam;
  ofFbo      fbo;
  map<string, embree::Handle<embree::Device::RTShape>> meshMap;

  size_t width;
  size_t height;

  int depth;                       //!< recursion depth
  int spp;                         //!< samples per pixel for ordinary rendering

  // output settings
  int   numBuffers;                //!< number of buffers of the framebuffer
  float gamma;
  bool  bVignetting;

  // rendering settings
  string accel;
  string tri;

  // display settings
  bool resetAccumulation;
  int  accumulation;
  int  numPassesMax;

  //stats
  float lastRenderTime;
  float accumulatedTime;

  // internal object
  embree::Handle<embree::Device::RTCamera>            camera;
  embree::Handle<embree::Device::RTRenderer>          renderer;
  embree::Handle<embree::Device::RTToneMapper>        tonemapper;
  embree::Handle<embree::Device::RTFrameBuffer>       frameBuffer;
  embree::Handle<embree::Device::RTImage>             backplate;
  embree::Handle<embree::Device::RTScene>             render_scene;
  vector<embree::Handle<embree::Device::RTPrimitive>> prims;

  map<embree::Device::RTShape, embree::Handle<embree::Device::RTPrimitive>> primsMap;

  ofMatrix4x4 identity;
};

Renderer::Renderer() {
  width  = 512;
  height = 512;

  depth      = 2;
  spp        = 1;
  gamma      = 1.0f;
  numBuffers = 1;

  accel      = "default";
  tri        = "default";

  resetAccumulation = false;
  accumulation      = 0;

  numPassesMax      = 0;

  lastRenderTime    = 0.0f;
  accumulatedTime   = 0.0f;
}

Renderer::~Renderer() {
}

void Renderer::setup(ofCamera & cam_, int numThreads) {
  cam = &cam_;

  embree::gEmbreeDevice = embree::Device::rtCreateDevice("default", numThreads);

  renderer = embree::gEmbreeDevice->rtNewRenderer("pathtracer");
  if (depth >= 0) {
    embree::gEmbreeDevice->rtSetInt1(renderer, "maxDepth", depth);
  }
  embree::gEmbreeDevice->rtSetInt1(renderer, "sampler.spp", spp);
  embree::gEmbreeDevice->rtSetInt1(renderer, "printstats", 0);
  embree::gEmbreeDevice->rtCommit(renderer);

  tonemapper = embree::gEmbreeDevice->rtNewToneMapper("default");
  embree::gEmbreeDevice->rtSetFloat1(tonemapper, "gamma", gamma);
  embree::gEmbreeDevice->rtCommit(tonemapper);

  frameBuffer = embree::gEmbreeDevice->rtNewFrameBuffer("RGB_FLOAT32", width, height, numBuffers);

  camera = embree::gEmbreeDevice->rtNewCamera("depthoffield");

  fbo.allocate(width, height, GL_RGB);

  backplate = NULL;
}

void Renderer::exit() {
  delete embree::gEmbreeDevice;
}

void Renderer::buildScene() {
  render_scene = createScene();
  reset();
}

void Renderer::reset() {
  accumulatedTime   = 0;
  resetAccumulation = true;
}

void Renderer::update(float focalDistance, float lensRadius) {
  if (resetAccumulation) {
    accumulation      = 0;
    resetAccumulation = false;
  }

  if (numPassesMax > 0 && accumulation >= numPassesMax) {
    return;
  }

  updateCamera(focalDistance, lensRadius);

  double t = embree::getSeconds();
  embree::gEmbreeDevice->rtRenderFrame(renderer, camera, render_scene, tonemapper, frameBuffer, accumulation);
  lastRenderTime = embree::getSeconds() - t;
  accumulatedTime += lastRenderTime;
  embree::gEmbreeDevice->rtSwapBuffers(frameBuffer);

  accumulation++;
}

void Renderer::renderGL() {
  if (numPassesMax > 0 && accumulation >= numPassesMax) {
    return;
  }

  void * ptr = embree::gEmbreeDevice->rtMapFrameBuffer(frameBuffer);

  fbo.begin();

  glPixelZoom(-1.0f, 1.0f);
  glRasterPos2f((GLsizei)width - 0.1f, 0.1f);
  glDrawPixels((GLsizei)width, (GLsizei)height, GL_RGBA, GL_FLOAT, ptr);

  fbo.end();

  embree::gEmbreeDevice->rtUnmapFrameBuffer(frameBuffer);
}

void Renderer::draw(int x, int y) {
  fbo.draw(x, y);
}

#pragma mark - stats

int Renderer::getAccumulation() {
  return accumulation;
}

float Renderer::getLastRenderTime() {
  return lastRenderTime;
}

float Renderer::getAccumulatedTime() {
  return accumulatedTime;
}

#pragma mark - scene

void Renderer::loadScene(string filepath) {
  std::vector<embree::Device::RTPrimitive> p = embree::loadScene(ofToDataPath(filepath, true), embree::gEmbreeDevice);
  prims.insert(prims.end(), p.begin(), p.end());
  reset();
}

void Renderer::loadBackPlate(string filepath) {
  backplate = loadImage(ofToDataPath(filepath, true), embree::gEmbreeDevice);
  embree::gEmbreeDevice->rtSetImage(renderer, "backplate", backplate);
  embree::gEmbreeDevice->rtCommit(renderer);
  reset();
}

embree::Handle<embree::Device::RTShape> Renderer::addSphere(embree::Device::RTMaterial material, ofSpherePrimitive & spherePr) {
  ofPoint p = spherePr.getPosition();

  embree::Handle<embree::Device::RTShape> sphere = embree::gEmbreeDevice->rtNewShape("sphere");
  embree::gEmbreeDevice->rtSetFloat3(sphere, "P", p.x, p.y, p.z);
  embree::gEmbreeDevice->rtSetFloat1(sphere, "r", spherePr.getRadius());
  embree::gEmbreeDevice->rtSetInt1(sphere, "numTheta", spherePr.getResolution());
  embree::gEmbreeDevice->rtSetInt1(sphere, "numPhi"  , spherePr.getResolution());
  embree::gEmbreeDevice->rtCommit(sphere);

  return addShape(material, sphere);
}

embree::Handle<embree::Device::RTShape> Renderer::addSphere(embree::Device::RTMaterial material, ofPoint pos, float radius, int numTheta, int numPhi) {
  embree::Handle<embree::Device::RTShape> sphere = embree::gEmbreeDevice->rtNewShape("sphere");
  embree::gEmbreeDevice->rtSetFloat3(sphere, "P", pos.x, pos.y, pos.z);
  // embree::gEmbreeDevice->rtSetFloat3(sphere, "dPdt", dPdt.x, dPdt.y, dPdt.z);
  embree::gEmbreeDevice->rtSetFloat1(sphere, "r", radius);
  embree::gEmbreeDevice->rtSetInt1(sphere, "numTheta", numTheta);
  embree::gEmbreeDevice->rtSetInt1(sphere, "numPhi"  , numPhi);
  embree::gEmbreeDevice->rtCommit(sphere);

  return addShape(material, sphere);
}

embree::Handle<embree::Device::RTShape> Renderer::addMesh(embree::Device::RTMaterial material, const ofMesh & meshPr) {
  return addMesh(material, meshPr, identity);
}

embree::Handle<embree::Device::RTShape> Renderer::addMesh(embree::Device::RTMaterial material, const ofMesh & meshPr, const ofMatrix4x4& transform) {
  int vertsNum  = meshPr.getNumVertices();
  int vertsSize = vertsNum * sizeof(ofVec3f);
  embree::Device::RTData positions = embree::gEmbreeDevice->rtNewData("immutable_managed", vertsSize, meshPr.getVerticesPointer());

  int normalsNum  = meshPr.getNumNormals();
  int normalsSize = normalsNum * sizeof(ofVec3f);
  embree::Device::RTData normals = embree::gEmbreeDevice->rtNewData("immutable_managed", normalsSize, meshPr.getNormalsPointer());

  int textcoordsNum  = meshPr.getNumTexCoords();
  int textcoordsSize = textcoordsNum * sizeof(ofVec2f);
  embree::Device::RTData texcoords = embree::gEmbreeDevice->rtNewData("immutable_managed", textcoordsSize, meshPr.getTexCoordsPointer());

  int indicesNum  = meshPr.getNumIndices();
  int indicesSize = indicesNum * sizeof(ofIndexType);
  embree::Device::RTData triangles = embree::gEmbreeDevice->rtNewData("immutable_managed", indicesSize, meshPr.getIndexPointer());

  embree::Handle<embree::Device::RTShape> mesh = embree::gEmbreeDevice->rtNewShape("trianglemesh");

  if (vertsNum) {
    embree::gEmbreeDevice->rtSetArray(mesh, "positions", "float3", positions, vertsNum, sizeof(ofVec3f), 0);
  }
  if (normalsNum) {
    embree::gEmbreeDevice->rtSetArray(mesh, "normals", "float3", normals, normalsNum, sizeof(ofVec3f), 0);
  }
  if (textcoordsNum) {
    embree::gEmbreeDevice->rtSetArray(mesh, "texcoords", "float2", texcoords, textcoordsNum, sizeof(ofVec2f), 0);
  }
  if (indicesNum) {
    embree::gEmbreeDevice->rtSetArray(mesh, "indices", "int3", triangles, indicesNum / 3, 3 * sizeof(ofIndexType), 0);
  }

  embree::gEmbreeDevice->rtCommit(mesh);
  embree::gEmbreeDevice->rtClear(mesh);

  return addShape(material, mesh, transform);
}

embree::Handle<embree::Device::RTShape> Renderer::addShape(embree::Device::RTMaterial material, embree::Device::RTShape shape) {
  return addShape(material, shape, embree::AffineSpace3f(embree::one));
}

embree::Handle<embree::Device::RTShape> Renderer::addShape(embree::Device::RTMaterial material, embree::Device::RTShape shape, const ofMatrix4x4 & t) {
  embree::AffineSpace3f space = embree::AffineSpace3f(
    embree::LinearSpace3f(t(0,0), t(1,0), t(2,0),
                          t(0,1), t(1,1), t(2,1),
                          t(0,2), t(1,2), t(2,2)),
    embree::Vec3f(t(3,0), t(3,1), t(3,2))
  );
  return addShape(material, shape, space);
}

embree::Handle<embree::Device::RTShape> Renderer::addShape(embree::Device::RTMaterial material, embree::Device::RTShape shape, const embree::AffineSpace3f & transform) {
  embree::Handle<embree::Device::RTPrimitive> prim = embree::gEmbreeDevice->rtNewShapePrimitive(shape, material, embree::copyToArray(transform));
  prims.push_back(prim);
  primsMap[shape] = prim;
  buildScene();
  return shape;
}

void Renderer::removeShapes(vector<embree::Handle<embree::Device::RTShape>> & shapes) {
  for (int i = 0; i < shapes.size(); i += 1) {
    removeShape(shapes[i]);
  }
}

void Renderer::removeShape(embree::Device::RTShape shape) {
  vector<embree::Handle<embree::Device::RTPrimitive>>::iterator it;
  embree::Device::__RTPrimitive * a;
  embree::Device::__RTPrimitive * b;
  for (it = prims.begin(); it != prims.end(); ++it) {
    a = (embree::Device::__RTPrimitive *)(*it);
    b = (embree::Device::__RTPrimitive *)primsMap[shape];
    // ofLog() << a << " " << b << " " << (a == b);
  }
}

void Renderer::clearScene(bool bRebuild) {
  prims.erase(prims.begin(), prims.end());
  if (bRebuild) {
    buildScene();
  }
}

#pragma mark - lights

void Renderer::addHDRILight(string filepath, ofColor luminosity) {
  embree::Handle<embree::Device::RTLight> light = embree::gEmbreeDevice->rtNewLight("hdrilight");
  const embree::Col3f L = toEmbreeCol3f(luminosity);
  embree::gEmbreeDevice->rtSetFloat3(light, "L", L.r, L.g, L.b);
  embree::gEmbreeDevice->rtSetImage(light, "image", loadImage(ofToDataPath(filepath, true), embree::gEmbreeDevice));
  embree::gEmbreeDevice->rtCommit(light);
  prims.push_back(embree::gEmbreeDevice->rtNewLightPrimitive(light, NULL));
}

void Renderer::addQuadLight(ofPoint P, ofPoint U, ofPoint V, ofColor L) {
  embree::Handle<embree::Device::RTLight> light0 = embree::gEmbreeDevice->rtNewLight("trianglelight");
  embree::gEmbreeDevice->rtSetFloat3(light0, "v0", P.x + U.x + V.x, P.y + U.y + V.y, P.z + U.z + V.z);
  embree::gEmbreeDevice->rtSetFloat3(light0, "v1", P.x + U.x, P.y + U.y, P.z + U.z);
  embree::gEmbreeDevice->rtSetFloat3(light0, "v2", P.x, P.y, P.z);
  embree::gEmbreeDevice->rtSetFloat3(light0, "L",  L.r, L.g, L.b);
  embree::gEmbreeDevice->rtCommit(light0);
  prims.push_back(embree::gEmbreeDevice->rtNewLightPrimitive(light0, NULL));

  embree::Handle<embree::Device::RTLight> light1 = embree::gEmbreeDevice->rtNewLight("trianglelight");
  embree::gEmbreeDevice->rtSetFloat3(light1, "v0", P.x + U.x + V.x, P.y + U.y + V.y, P.z + U.z + V.z);
  embree::gEmbreeDevice->rtSetFloat3(light1, "v1", P.x, P.y, P.z);
  embree::gEmbreeDevice->rtSetFloat3(light1, "v2", P.x + V.x, P.y + V.y, P.z + V.z);
  embree::gEmbreeDevice->rtSetFloat3(light1, "L",  L.r, L.g, L.b);
  embree::gEmbreeDevice->rtCommit(light1);
  prims.push_back(embree::gEmbreeDevice->rtNewLightPrimitive(light1, NULL));
}

#pragma mark - properties

void Renderer::setGamma(float gamma_) {
  gamma = gamma_;
  embree::gEmbreeDevice->rtSetFloat1(tonemapper, "gamma", gamma);
  embree::gEmbreeDevice->rtCommit(tonemapper);
}

void Renderer::setSize(int width_, int height_) {
  width  = width_;
  height = height_;
  frameBuffer = embree::gEmbreeDevice->rtNewFrameBuffer("RGB_FLOAT32", width, height, 1);

  fbo.allocate(width, height, GL_RGB);
  fbo.begin();
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  fbo.end();
}

void Renderer::setClampRadiance(float clampRadianceIfMoreThan, float clampRadianceTo) {
  embree::gEmbreeDevice->rtSetFloat1(renderer, "clampRadianceIfMoreThan", clampRadianceIfMoreThan);
  embree::gEmbreeDevice->rtSetFloat1(renderer, "clampRadianceTo", clampRadianceTo);
  embree::gEmbreeDevice->rtCommit(renderer);
}

void Renderer::setSamplePerPixel(int spp_) {
  spp = spp_;
  embree::gEmbreeDevice->rtSetInt1(renderer, "sampler.spp", spp);
  embree::gEmbreeDevice->rtCommit(renderer);
}

void Renderer::setRecursionDepth(int depth_) {
  depth = depth_;
  embree::gEmbreeDevice->rtSetInt1(renderer, "maxDepth", depth);
  embree::gEmbreeDevice->rtCommit(renderer);
}

void Renderer::setVignetting(bool bVignetting_) {
  bVignetting = bVignetting_;
  embree::gEmbreeDevice->rtSetBool1(tonemapper, "vignetting", bVignetting);
  embree::gEmbreeDevice->rtCommit(tonemapper);
}

void Renderer::setMaxPasses(int numPassMax_) {
  numPassesMax = numPassMax_;
}

#pragma mark - internal

void Renderer::updateCamera(float focalDistance, float lensRadius) {
  embree::Vec3f p = toEmbreeVec3f(cam->getPosition());
  embree::Vec3f t = toEmbreeVec3f(cam->getPosition() + cam->getLookAtDir());
  embree::Vec3f u = toEmbreeVec3f(cam->getUpDir());
  embree::AffineSpace3f camSpace = embree::AffineSpace3f::lookAtPoint(p, t, u);

  embree::gEmbreeDevice->rtSetTransform(camera, "local2world", embree::copyToArray(camSpace));
  embree::gEmbreeDevice->rtSetFloat1(camera, "angle", cam->getFov());
  embree::gEmbreeDevice->rtSetFloat1(camera, "aspectRatio", float(width) / float(height));
  embree::gEmbreeDevice->rtSetFloat1(camera, "lensRadius", lensRadius);
  embree::gEmbreeDevice->rtSetFloat1(camera, "focalDistance", focalDistance);
  embree::gEmbreeDevice->rtCommit(camera);
}

embree::Handle<embree::Device::RTScene> Renderer::createScene() {
  embree::Device::RTPrimitive * p = (embree::Device::RTPrimitive *)(prims.size() == 0 ? NULL : &prims[0]);
  // ofLog() << "Renderer::createScene - creating scene with " << prims.size() << " primitives";
  return embree::gEmbreeDevice->rtNewScene((accel + " " + tri).c_str(), p, prims.size());
}

} // namespace Embree

typedef map<embree::Device::RTShape, ofSpherePrimitive *> SphereMap;
typedef map<embree::Device::RTShape, of3dPrimitive *>     MeshMap;

int              renderingMode         = 0;
uint64_t         randomMaterialCounter = 0;
Embree::Renderer renderer;
ofEasyCam        cam;
SphereMap        spheresMap;
MeshMap          meshMap;

static void addSphere(ofPoint pos, float radius) {
  embree::Handle<embree::Device::RTMaterial> material = embree::gEmbreeDevice->rtNewMaterial("MetallicPaint");
  embree::gEmbreeDevice->rtSetFloat1(material, "eta", 1.45f);
  embree::gEmbreeDevice->rtSetFloat3(material, "glitterColor", 0.5, 0.44, 0.42);
  embree::gEmbreeDevice->rtSetFloat1(material, "glitterSpread", 0.01f);
  embree::gEmbreeDevice->rtSetFloat3(material, "shadeColor", ofRandom(1.f), ofRandom(1.f), ofRandom(1.f));
  embree::gEmbreeDevice->rtCommit(material);

  ofSpherePrimitive * sphere = new ofSpherePrimitive();
  sphere->setRadius(radius);
  sphere->setPosition(pos);
  embree::Device::RTShape shape = renderer.addSphere(material, *sphere);
  spheresMap[shape] = sphere;
}

static void addSphereNatively(ofPoint pos, float radius) {
  // create a red velvet material
  embree::Handle<embree::Device::RTMaterial> material = embree::gEmbreeDevice->rtNewMaterial("Velvet");
  embree::gEmbreeDevice->rtSetFloat3(material, "reflectance", 0.4, 0, 0);
  embree::gEmbreeDevice->rtSetFloat1(material, "backScattering", 0.5f);
  embree::gEmbreeDevice->rtSetFloat3(material, "horizonScatteringColor", 0.75, 0.1, 0.1);
  embree::gEmbreeDevice->rtSetFloat1(material, "horizonScatteringFallOff", 10.0f);
  embree::gEmbreeDevice->rtCommit(material);

  // create a sphere shape
  embree::Handle<embree::Device::RTShape> sphere = embree::gEmbreeDevice->rtNewShape("sphere");
  embree::gEmbreeDevice->rtSetFloat3(sphere, "P", pos.x, pos.y, pos.z);
  embree::gEmbreeDevice->rtSetFloat1(sphere, "r", radius);
  embree::gEmbreeDevice->rtSetInt1(sphere, "numTheta", 50);
  embree::gEmbreeDevice->rtSetInt1(sphere, "numPhi"  , 50);
  embree::gEmbreeDevice->rtCommit(sphere);

  // create a shape primitive
  embree::AffineSpace3f transform = embree::AffineSpace3f(embree::one);
  embree::Handle<embree::Device::RTPrimitive> prim = embree::gEmbreeDevice->rtNewShapePrimitive(sphere, material, embree::copyToArray(transform));

  // add the primitive to the renderer "display list"
  renderer.getPrimitives().push_back(prim);

  // rebuild the scene
  renderer.buildScene();

  // keep the OF scene in sync
  ofSpherePrimitive * sphereOF = new ofSpherePrimitive();
  sphereOF->setRadius(radius);
  sphereOF->setPosition(pos);
  spheresMap[sphere] = sphereOF;
}

static void addBox(ofPoint pos, ofPoint rot) {
  embree::Handle<embree::Device::RTMaterial> material = embree::gEmbreeDevice->rtNewMaterial("Metal");
  embree::gEmbreeDevice->rtSetFloat3(material, "eta", 0.19, 0.45, 1.50);
  embree::gEmbreeDevice->rtSetFloat3(material, "k", 3.06, 2.40, 1.88);
  embree::gEmbreeDevice->rtSetFloat1(material, "roughness", 0.005f);
  embree::gEmbreeDevice->rtCommit(material);

  ofBoxPrimitive * box = new ofBoxPrimitive();
  box->set(150, 150, 150);
  box->setPosition(pos);
  box->setOrientation(rot);
  embree::Device::RTShape shape = renderer.addMesh(material, box->getMesh(), box->getGlobalTransformMatrix());
  meshMap[shape] = box;
}

static void addGround() {
  embree::Handle<embree::Device::RTMaterial> material = embree::gEmbreeDevice->rtNewMaterial("Matte");
  embree::gEmbreeDevice->rtSetFloat3(material, "reflectance", 1.f, 1.f, 1.f);
  embree::gEmbreeDevice->rtCommit(material);

  ofPlanePrimitive * plane = new ofPlanePrimitive();
  plane->set(1500, 1500, 10, 10, OF_PRIMITIVE_TRIANGLES);
  plane->setPosition(0, -400, 0);
  plane->rotate(90, 1, 0, 0);
  embree::Device::RTShape shape = renderer.addMesh(material, plane->getMesh(), plane->getGlobalTransformMatrix());
  meshMap[shape] = plane;
}

void ofApp::setup() {
  renderingMode = 0;
  cam.setDistance(600);
  cam.setFov(75);
    
  renderer.setup(cam);
  renderer.setSize(ofGetWidth(), ofGetHeight());
  renderer.setClampRadiance(23, 3);
  renderer.setRecursionDepth(20);

  renderer.addHDRILight("lines.ppm", ofColor(1.0,1.0,1.0));
  //renderer.addQuadLight(ofPoint(213, 548.77, 227), ofPoint(130, 0, 0), ofPoint(0, 0, 105), ofColor(120, 120, 120));

  addSphere(ofPoint(50, 0, 0), 50);
  addSphereNatively(ofPoint(-50, 0, 0), 50);

  addGround();

  for (int i=0; i<5; i++) {
    ofPoint p = ofPoint(ofRandom(-400, 400), ofRandom(-400, 400), ofRandom(-400, 400));
    ofPoint r = ofPoint(ofRandom(360), ofRandom(360), ofRandom(360));
    addBox(p, r);

    ofPoint pos = ofPoint(ofRandom(-300, 300), ofRandom(-300, 300), ofRandom(-300, 300));
    addSphere(pos, ofRandom(25, 75));
  }
}

void ofApp::exit() {
  for (SphereMap::iterator it = spheresMap.begin(); it!=spheresMap.end(); ++it) {
    delete it->second;
  }
  for (MeshMap::iterator it = meshMap.begin(); it!=meshMap.end(); ++it) {
    delete it->second;
  }
}

void ofApp::update() {
  if (renderingMode == 1) {
    renderer.update(700, 1);
    renderer.renderGL();
  }
}

void ofApp::draw() {
  string text;

  // Draw GL render

  cam.begin(ofRectangle(0, 0, ofGetWidth(), ofGetHeight()));
  for (SphereMap::iterator it = spheresMap.begin(); it != spheresMap.end(); ++it) {
    ofSpherePrimitive * s = it->second;
    s->draw(OF_MESH_WIREFRAME);
  }
  for (MeshMap::iterator it = meshMap.begin(); it != meshMap.end(); ++it) {
    of3dPrimitive * s = it->second;
    s->draw(OF_MESH_WIREFRAME);
  }
  cam.end();

  text = "Press 's' to add a random sphere\n";
  text += "Press 'f' to toggle rendering mode";
  ofDrawBitmapStringHighlight(text, 10, 20);

  // Draw Embree render

  if (renderingMode == 1) {
    renderer.draw(0, 0);
  }

  text = "FPS : " + ofToString(ofGetFrameRate()) + "\n";
  text += "Last render time : " + ofToString(renderer.getLastRenderTime()*1000, 0) + "ms" + "\n";
  text += "Accumulated passes : " + ofToString(renderer.getAccumulation()) + "\n";
  text += "Accumulated time : " + ofToString(renderer.getAccumulatedTime(), 0) + "s";

  ofDrawBitmapStringHighlight(text, ofGetWidth()*0.5+10, 20);
}

void ofApp::keyPressed(int key) {
  switch(key) {
    case 'f':
      {
        renderingMode = renderingMode == 0 ? 1 : 0;
        if (renderingMode == 1) {
          renderer.reset();
        }
      }
      break;
    case 's':
      {
        ofPoint pos = ofPoint(ofRandom(-200, 200), ofRandom(-200, 200), ofRandom(-200, 200));
        addSphere(pos, ofRandom(25, 75));
      }
      break;
    default:
      break;
  }
}

void ofApp::windowResized(int w, int h) {
  renderer.setSize(w, h);
}

void ofApp::mouseDragged(int x, int y, int button) {
  renderer.reset();
}

void ofApp::mouseReleased(int x, int y, int button) {
  renderer.reset();
}